﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace BReusable.StaticReflection
{
	/// <summary>
	/// From BReusable
	/// Avoid this for high performance sections
	/// http://blogs.msdn.com/alexj/archive/2008/03/03/maybe-there-is-more.aspx
	/// Also
	/// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
	/// </summary>
	public static  class Walk
	{


		private static readonly MethodInfo maybeMethod;
		/// <summary>
		/// For types that return a nullable
		/// </summary>
		private static readonly MethodInfo maybeNullableMethod;

		///// <summary>
		///// For types that return a primitive (not nullable)
		///// to convert them to nullable
		///// </summary>
		//private static readonly MethodInfo maybePrimitiveMethod;

		static Walk()
		{
			Expression<Func<object>> exp = () => Walk.MaybeShallow<object, object>(null, null);
			var name = Member.Name(exp);
			
			maybeMethod = typeof(Walk).GetMethod(name, BindingFlags.Public | BindingFlags.Static);

			Expression<Func<int?>> expNullable = () => Walk.MaybeShallowNullable<object, int?>(null, null);

			maybeNullableMethod=typeof(Walk).GetMethod(Member.Name(()=>Walk.MaybeShallowNullable<object,
				Nullable<int>>(null,null)));
			//maybePrimitiveMethod = typeof(Walk).GetMethod(Member.Name(() => Walk.MaybeShallowPrimitive<object, int>(null, null)));
		}

		/// <summary>
		/// For expressions that return a nullable types
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V">Nullable<></typeparam>
		/// <param name="t"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static V MaybeShallowNullable<T, V>(this T t, Expression<Func<T, V>> expression)
			where T:class
//			where V:struct
		{
			if (typeof(V).IsNullable() == false)
				throw new ArgumentException(Member.Name(() => MaybeShallowNullable(string.Empty, x => -1)) + " is for nullable types");
			if (t == null)
				return default(V);
			return expression.Compile()(t);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="t"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		[Obsolete]
		public static V? MaybeShallowPrimitive<T, V>(this T t, Expression<Func<T, V>> expression)
			where T:class
			where V:struct
		{
			if (typeof(V).IsNullable())
				throw new ArgumentException(Member.Name(()=>MaybeShallowPrimitive(string.Empty,x=>-1))+ " is not for nullables");
			if (t != null)
				return expression.Compile()(t);
			return new Nullable<V>();
		}

		public static V? MaybeDeepToNullable<T,TCurry, V>(this T t, Expression<Func<T, TCurry>> expression,Func<TCurry,V> finalAccessor)
			where T:class
			where V:struct
		{
			var curry = t.MaybeDeep(expression);
			if (curry != null)
				return new Nullable<V>( finalAccessor(curry));
			return null;
		}
		

		/// <summary>
		/// Goes as deep as the expression so x.MaybeDeep<Z,X>( x=>x.y.z)
		/// Inspired from
		/// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="t"></param>
		/// <param name="ex"></param>
		/// <returns></returns>
		public static V MaybeDeep<T, V>(this T t, Expression<Func<T, V>> ex)
			where T : class
		{
			if (typeof(V).IsClass == false && typeof(V).IsNullable() == false)
				throw new ArgumentException(Member.Name(() => MaybeDeep(string.Empty, x => string.Empty)) + " does not support primitives");
			// It handles only the case of the demo
			if (ex.Body is MemberExpression)
			{
				MethodCallExpression memberEx = ConvertMemberToMethodCall(ex.Body as MemberExpression);

				LambdaExpression lambda = Expression.Lambda(memberEx, new ParameterExpression[] { ex.Parameters[0] });

				//Changed from as V to support things that are not classes
				return (V)lambda.Compile().DynamicInvoke(new object[] { t });

			}

			else if (ex.Body is NewExpression)
			{
				NewExpression newExp = (NewExpression)ex.Body;
				
				return ex.Compile()(t);
			} else
			{
				throw new NotSupportedException("");
			}

		}
		
		/// <summary>
		/// Only goes one level in so x.MaybeShawllow(x=>x.y)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="t"></param>
		/// <param name="selector"></param>
		/// <returns></returns>
		public static V MaybeShallow<T, V>(this T t, Func<T, V> selector)
			where T : class
			where V : class
		{
			if (t == null) return null;
			return selector(t);	
		}

		/// <summary>
		/// This method converts a call from a member in method call. Basically:
		/// '. MaProp' becomes '. Maybe (p => p.MaProp)'
		/// </summary>
		/// <param name="memberExpression"></param>
		/// <returns></returns>
		private static MethodCallExpression ConvertMemberToMethodCall(MemberExpression memberExpression)
		{
            Expression ex = null ;
 
            // The recursive call is made here
            if (memberExpression.Expression is MemberExpression )
			{
				ex = ConvertMemberToMethodCall (memberExpression.Expression as MemberExpression );
			}
            else
			{
ex = memberExpression.Expression;
			}
 
            // A Generic method retrieves the "Maybe"
            MethodInfo methodInfo = maybeMethod;
			Type type = null;

            //default case is a property
 
            if(memberExpression.Member is PropertyInfo)
			{
				var prop=(PropertyInfo)memberExpression.Member;
				type = prop.PropertyType;
			} else
			 if (memberExpression.Member is FieldInfo)
			{
				var field = (FieldInfo)memberExpression.Member;
				type = field.FieldType;
			}
			else
			{
				throw new NotImplementedException("");
			}

			if (type.IsNullable())
				methodInfo = maybeNullableMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
			else if (type.IsPrimitive)
			{
				//methodInfo = maybePrimitiveMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
				throw new NotSupportedException("Primitives are not supported");
			}
			else //class
			{
				// Obligatory passage: get a version of the standard method "Maybe"
				methodInfo = methodInfo.MakeGenericMethod(
					new Type[] { memberExpression.Member.DeclaringType, type });
			}

            // Create a parameter to the lambda passed in parameter Maybe
            ParameterExpression p = Expression . Parameter (memberExpression.Member.DeclaringType, "p" );
 
            // Create the lambda
            LambdaExpression maybeLamba = Expression . Lambda (
                    Expression . MakeMemberAccess (p, memberExpression.Member),
                    new ParameterExpression []{p});
 
            // Create the call Maybe
            MethodCallExpression result = Expression . Call (
                null,methodInfo,
                new Expression [] {ex, maybeLamba});
            return result;
		}
	}
}



